package org.pentaho.di.trans.steps.rest;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.isyscore.os.metadata.kettle.step.RestStep;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.api.uri.UriComponent;
import com.sun.jersey.client.apache4.ApacheHttpClient4;
import com.sun.jersey.client.apache4.config.ApacheHttpClient4Config;
import com.sun.jersey.client.apache4.config.DefaultApacheHttpClient4Config;
import com.sun.jersey.client.urlconnection.HTTPSProperties;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.jetbrains.annotations.Nullable;
import org.json.simple.JSONObject;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.encryption.Encr;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.row.RowDataUtil;
import org.pentaho.di.core.row.RowMeta;
import org.pentaho.di.core.util.StringUtil;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransHopMeta;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.*;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

/**
 * @author Samatar
 * @since 16-jan-2011
 */

public class Rest extends BaseStep implements StepInterface {
    private static Class<?> PKG = RestMeta.class; // for i18n purposes, needed by Translator2!! $NON-NLS-1$

    private RestMeta meta;
    private RestData data;
    private AtomicBoolean firstCome = new AtomicBoolean(true);
    private boolean isFirtStep;
    public Rest(StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans) {
        super(stepMeta, stepDataInterface, copyNr, transMeta, trans);

        isFirtStep = isFirstStep(getTransMeta(),stepMeta);
    }

    /* for unit test*/
    MultivaluedMapImpl createMultivalueMap(String paramName, String paramValue) {
        MultivaluedMapImpl queryParams = new MultivaluedMapImpl();
        queryParams.add(paramName, UriComponent.encode(paramValue, UriComponent.Type.QUERY_PARAM));
        return queryParams;
    }

    protected Object[] callRest(RestParam restParam) throws KettleException {
        if (isBasic()) {
            log.logBasic(JSON.toJSONString(restParam));
        }
        data.realUrl = restParam.getUrl();
        data.method = restParam.getMethod();
        WebResource webResource = null;
        Client client = null;
        Object[] newRow = null;
        try {
            if (isDetailed()) {
                logDetailed(BaseMessages.getString(PKG, "Rest.Log.ConnectingToURL", data.realUrl));
            }
            // Register a custom StringMessageBodyWriter to solve PDI-17423
            MessageBodyWriter<String> stringMessageBodyWriter = new StringMessageBodyWriter();
            data.config.getSingletons().add(stringMessageBodyWriter);
            // create an instance of the com.sun.jersey.api.client.Client class
            client = ApacheHttpClient4.create(data.config);
            if (data.basicAuthentication != null) {
                client.addFilter(data.basicAuthentication);
            }
            // create a WebResource object, which encapsulates a web resource for the client
            webResource = client.resource(data.realUrl);

            // used for calculating the responseTime
            long startTime = System.currentTimeMillis();

            if (isDebug()) {
                logDebug(BaseMessages.getString(PKG, "Rest.Log.ConnectingToURL", webResource.getURI()));
            }
            WebResource.Builder builder = webResource.getRequestBuilder();
            String contentType = "application/json"; // media type override, if not null
            if (data.useHeaders) {
                // Add headers
                List<RestParam.Info> headers = restParam.getHeaders();
                for (RestParam.Info header : headers) {
                    // unsure if an already set header will be returned to builder
                    builder = builder.header(header.getName(), header.getField());
                    if ("Content-Type".equals(header.getName())) {
                        contentType = header.getField();
                    }
                    if (isDebug()) {
                        logDebug(BaseMessages.getString(PKG, "Rest.Log.HeaderValue", header.getName(), header.getField()));
                    }
                }
            }

            ClientResponse response = null;
            String entityString = null;
            if (data.useBody) {
                // Set Http request entity
                entityString = Const.NVL(restParam.getBody(), null);
                if (isDebug()) {
                    logDebug(BaseMessages.getString(PKG, "Rest.Log.BodyValue", entityString));
                }
            }
            try {
                if (data.method.equals(RestMeta.HTTP_METHOD_GET)) {
                    response = builder.get(ClientResponse.class);
                } else if (data.method.equals(RestMeta.HTTP_METHOD_POST)) {
                    if (null != contentType) {
                        response = builder.type(contentType).post(ClientResponse.class, entityString);
                    } else {
                        response = builder.type(data.mediaType).post(ClientResponse.class, entityString);
                    }
                } else if (data.method.equals(RestMeta.HTTP_METHOD_PUT)) {
                    if (null != contentType) {
                        response = builder.type(contentType).put(ClientResponse.class, entityString);
                    } else {
                        response = builder.type(data.mediaType).put(ClientResponse.class, entityString);
                    }
                } else if (data.method.equals(RestMeta.HTTP_METHOD_DELETE)) {
                    response = builder.delete(ClientResponse.class);
                } else if (data.method.equals(RestMeta.HTTP_METHOD_HEAD)) {
                    response = builder.head();
                } else if (data.method.equals(RestMeta.HTTP_METHOD_OPTIONS)) {
                    response = builder.options(ClientResponse.class);
                } else if (data.method.equals(RestMeta.HTTP_METHOD_PATCH)) {
                    if (null != contentType) {
                        response = builder.type(contentType).method(RestMeta.HTTP_METHOD_PATCH, ClientResponse.class, entityString);
                    } else {
                        response = builder.type(data.mediaType).method(RestMeta.HTTP_METHOD_PATCH, ClientResponse.class,
                                entityString);
                    }
                } else {
                    throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.UnknownMethod", data.method));
                }
            } catch (UniformInterfaceException u) {
                response = u.getResponse();
            }
            // Get response time
            long responseTime = System.currentTimeMillis() - startTime;
            if (isDetailed()) {
                logDetailed(BaseMessages.getString(PKG, "Rest.Log.ResponseTime", String.valueOf(responseTime), data.realUrl));
            }

            // Get status
            int status = response.getStatus();
            // Display status code
            if (isDebug()) {
                logDebug(BaseMessages.getString(PKG, "Rest.Log.ResponseCode", "" + status));
            }

            // Get Response
            String body;
            String headerString = null;
            try {
                body = response.getEntity(String.class);
                if (isBasic()) {
                    //TODO 避免body太长判断body的长度，过长的进行缩略打印，减少日志的大小
                    log.logBasic(JSON.toJSONString(body));
                }
            } catch (UniformInterfaceException ex) {
                body = "";
            }
            // get Header
            MultivaluedMap<String, String> headers = searchForHeaders(response);
            JSONObject json = new JSONObject();
            for (java.util.Map.Entry<String, List<String>> entry : headers.entrySet()) {
                String name = entry.getKey();
                List<String> value = entry.getValue();
                if (value.size() > 1) {
                    json.put(name, value);
                } else {
                    json.put(name, value.get(0));
                }
            }
            headerString = json.toJSONString();
            // for output
            int returnFieldsOffset = data.inputRowMeta.size();
            // add response to output
            if (!Utils.isEmpty(data.resultFieldName)) {
                newRow = RowDataUtil.addValueData(newRow, returnFieldsOffset, body);
                returnFieldsOffset++;
            }

            // add status to output
            if (!Utils.isEmpty(data.resultCodeFieldName)) {
                newRow = RowDataUtil.addValueData(newRow, returnFieldsOffset, new Long(status));
                returnFieldsOffset++;
            }

            // add response time to output
            if (!Utils.isEmpty(data.resultResponseFieldName)) {
                newRow = RowDataUtil.addValueData(newRow, returnFieldsOffset, new Long(responseTime));
                returnFieldsOffset++;
            }
            // add response header to output
            if (!Utils.isEmpty(data.resultHeaderFieldName)) {
                newRow = RowDataUtil.addValueData(newRow, returnFieldsOffset, headerString);
            }
        } catch (Exception e) {
            throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.CanNotReadURL", data.realUrl), e);
        } finally {
            if (webResource != null) {
                webResource = null;
            }
            if (client != null) {
                client.destroy();
            }
        }
        return newRow;
    }

    /**
     * 构建请求url
     *
     * @param url    www.baidu.com
     * @param params key val
     * @return
     */
    public static String getRequestUrl(String url, List<RestParam.Info> params) {
        StringBuilder builder = new StringBuilder(url);
        boolean isFirst = true;
        if (params == null || params.size() == 0) {
            return url;
        }
        if (url.contains("?")) {
            isFirst = false;
        }
        for (RestParam.Info info : params) {
            if (isFirst) {
                isFirst = false;
                builder.append("?");
            } else {
                builder.append("&");
            }
            builder.append(info.getName())
                    .append("=");
            if (info.getField() != null) {
                builder.append(info.getField());
            }

        }
        return builder.toString();
    }

    private void setConfig() throws KettleException {
        if (data.config == null) {
            // Use ApacheHttpClient for supporting proxy authentication.
            data.config = new DefaultApacheHttpClient4Config();
            if (!Utils.isEmpty(data.realProxyHost)) {
                // PROXY CONFIGURATION
                data.config.getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_URI, "http://" + data.realProxyHost + ":" + data.realProxyPort);
                if (!Utils.isEmpty(data.realHttpLogin) && !Utils.isEmpty(data.realHttpPassword)) {
                    AuthScope authScope = new AuthScope(data.realProxyHost, data.realProxyPort);
                    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(data.realHttpLogin, data.realHttpPassword);
                    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                    credentialsProvider.setCredentials(authScope, credentials);
                    data.config.getProperties().put(ApacheHttpClient4Config.PROPERTY_CREDENTIALS_PROVIDER, credentialsProvider);
                }
            } else {
                if (!Utils.isEmpty(data.realHttpLogin)) {
                    // Basic authentication
                    data.basicAuthentication = new HTTPBasicAuthFilter(data.realHttpLogin, data.realHttpPassword);
                }
            }
            if (meta.isPreemptive()) {
                data.config.getProperties().put(ApacheHttpClient4Config.PROPERTY_PREEMPTIVE_BASIC_AUTHENTICATION, true);
            }
            // SSL TRUST STORE CONFIGURATION
            if (!Utils.isEmpty(data.trustStoreFile)) {
                try (FileInputStream trustFileStream = new FileInputStream(data.trustStoreFile)) {
                    KeyStore trustStore = KeyStore.getInstance("JKS");
                    trustStore.load(trustFileStream, data.trustStorePassword.toCharArray());
                    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
                    tmf.init(trustStore);

                    SSLContext ctx = SSLContext.getInstance("SSL");
                    ctx.init(null, tmf.getTrustManagers(), null);

                    HostnameVerifier hv = new HostnameVerifier() {
                        public boolean verify(String hostname, SSLSession session) {
                            if (isDebug()) {
                                logDebug("Warning: URL Host: " + hostname + " vs. " + session.getPeerHost());
                            }
                            return true;
                        }
                    };
                    data.config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(hv, ctx));
                } catch (NoSuchAlgorithmException e) {
                    throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.NoSuchAlgorithm"), e);
                } catch (KeyStoreException e) {
                    throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.KeyStoreException"), e);
                } catch (CertificateException e) {
                    throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.CertificateException"), e);
                } catch (FileNotFoundException e) {
                    throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.FileNotFound", data.trustStoreFile), e);
                } catch (IOException e) {
                    throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.IOException"), e);
                } catch (KeyManagementException e) {
                    throw new KettleException(BaseMessages.getString(PKG, "Rest.Error.KeyManagementException"), e);
                }
            }
        }
    }

    protected MultivaluedMap<String, String> searchForHeaders(ClientResponse response) {
        return response.getHeaders();
    }

    @Override
    public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException {
        meta = (RestMeta) smi;
        data = (RestData) sdi;

        RestParam restParam = new RestParam();
        //************原生的是不支持rest作为第一个参数，一旦rest组件前面没有组件就需要手动处理,主要是兼容从启动参数中获取值***************
        if (!dynamicArgs(restParam)) {
            setOutputDone();
            firstCome.compareAndSet(false,true);
            return false;
        }
        if (first) {
            first = false;
            if(data.inputRowMeta==null){
                data.inputRowMeta=new RowMeta();
            }
            data.outputRowMeta = data.inputRowMeta.clone();
            meta.getFields(data.outputRowMeta, getStepname(), null, null, this, repository, metaStore);
            // set Headers
            if (CollectionUtils.isNotEmpty(restParam.getHeaders())) {
                data.useHeaders = true;
            }
            // Do we need to set body
            if (RestMeta.isActiveBody(restParam.getMethod())) {
                String field = restParam.getBody();
                if (!Utils.isEmpty(field)) {
                    data.useBody = true;
                }
            }
        }
        // end if first
        try {
            Object[] outputRowData = callRest(restParam);
            putRow(data.outputRowMeta, outputRowData);
            if (checkFeedback(getLinesRead())) {
                if (isDetailed()) {
                    logDetailed(BaseMessages.getString(PKG, "Rest.LineNumber") + getLinesRead());
                }
            }
        } catch (KettleException e) {
            boolean sendToErrorRow = false;
            String errorMessage = null;
            if (getStepMeta().isDoingErrorHandling()) {
                sendToErrorRow = true;
                errorMessage = e.toString();
            } else {
                logError(BaseMessages.getString(PKG, "Rest.ErrorInStepRunning") + e.getMessage());
                setErrors(1);
                logError(Const.getStackTracker(e));
                stopAll();
                setOutputDone(); // signal end to receiver(s)
                return false;
            }
            if (sendToErrorRow) {
                // Simply add this row to the error row
                putError(getInputRowMeta(), null, 1, errorMessage, null, "Rest001");
            }
        }

        return true;
    }

    @Nullable
    private boolean dynamicArgs(RestParam restParam) throws KettleException {
        Object[] r = getRow();
        if (r != null) {
            if(firstCome.compareAndSet(true,false)){
                if(data.inputRowMeta==null){
                    data.inputRowMeta=getInputRowMeta();
                }
            }
            handlePrefixNode(restParam, r);
            return true;
        }else if(isFirtStep) {
            //***********兼容rest为第一个节点，没有前置节点***********
            if (firstCome.compareAndSet(true,false)) {
                handleWithOutPrefixNode(restParam);
                return true;
            }
            return false;
        }
        return false;
    }

    /**
     * 处理带有前置节点的数据
     *
     * @param restParam 请求封装实体
     * @param r         上节点参数
     * @return
     * @throws KettleException
     */
    private RestParam handlePrefixNode(RestParam restParam, Object[] r) throws KettleException {

        String[] fieldNames = data.inputRowMeta.getFieldNames();
        Map<String, Object> localCache = new LinkedHashMap<>(fieldNames.length);
        IntStream.range(0, fieldNames.length).forEach(index -> {
            localCache.put(fieldNames[index], r[index]);
        });
        //****************1.整个url是个变量${url} 2.不包含${}的变量url 3.url中包含${}动态取值 4.url配置死了 ****************
        String dynamicUrl = meta.getUrl();
        String msg = "Rest.Error.urlMissing";
        if (StringUtils.isEmpty(dynamicUrl)) {
            throwMsg(msg, dynamicUrl);
        }
        doHandle(msg, dynamicUrl, localCache, (dynamicArgs) -> {
            restParam.setUrl(dynamicArgs);
        });
        //******************处理header field********************
        restParam.setHeaders(handleList("Rest.Error.headerErr", meta.getHeaderField(),
                meta.getHeaderName(), localCache));

        //******************处理param field*******************
        restParam.setParams(handleList("Rest.Error.paramErr", meta.getParameterField(),
                meta.getParameterName(), localCache));

        //******************raw********************
        String bodyField = meta.getBodyField();
        if (StringUtils.isNotEmpty(bodyField)) {
            doHandle("Rest.Error.bodyErr", bodyField, localCache, (dynamicArgs) -> {
                restParam.setBody(dynamicArgs);
            });
        }
        //******************method********************
        String method = meta.getMethod();
        restParam.setMethod(method);
        postHandleParam(restParam);
        return restParam;
    }

    /**
     * 处理不同方法不同参数处理
     *
     * @param restParam
     */
    private void postHandleParam(RestParam restParam) {
        if (RestMeta.HTTP_METHOD_GET.equals(restParam.getMethod()) && CollectionUtils.isNotEmpty(restParam.getParams())) {
            restParam.setUrl(getRequestUrl(restParam.getUrl(), restParam.getParams()));
        }
        if (!RestMeta.HTTP_METHOD_GET.equals(restParam.getMethod()) && CollectionUtils.isNotEmpty(restParam.getParams())) {
            restParam.setBody(transformFormData(restParam.getParams()));
        }
    }

    /**
     * 处理formdata 赋值body 此api组件暂时不需要文件上传
     *
     * @param formData
     * @return
     */
    private String transformFormData(List<RestParam.Info> formData) {
        if (CollectionUtils.isNotEmpty(formData)) {
            JSONObject jsonObject = new JSONObject();
            for (RestParam.Info info : formData) {
                String name = info.getName();
                String field = info.getField();
                jsonObject.put(name, field);
            }
            return jsonObject.toJSONString();
        }
        return "{}";
    }

    /**
     * rest为第一节点
     * 参数获取：
     * 1。task启动参数即variables(支持${}envionsubsti..()   支持直接取值getVariables())
     * 2.参数固定
     *
     * @return
     */
    private RestParam handleWithOutPrefixNode(RestParam restParam) throws KettleException {
        Map<String, Object> NilMap = Maps.newHashMap();
        //****************1.整个url是个变量${url} 2.不包含${}的变量url 3.url中包含${}动态取值 4.url配置死了 ****************
        String dynamicUrl = meta.getUrl();
        String msg = "Rest.Error.urlMissing";
        if (StringUtils.isEmpty(dynamicUrl)) {
            throwMsg(msg, dynamicUrl);
        }
        doHandle(msg, dynamicUrl, NilMap, (dynamicArgs) -> {
            restParam.setUrl(dynamicArgs);
        });
        //******************处理header field********************
        restParam.setHeaders(handleList("Rest.Error.headerErr", meta.getHeaderField(),
                meta.getHeaderName(), NilMap));

        //******************处理param field********************
        restParam.setParams(handleList("Rest.Error.paramErr", meta.getParameterField(),
                meta.getParameterName(), NilMap));

        //******************raw********************
        String bodyField = meta.getBodyField();
        if (StringUtils.isNotEmpty(bodyField)) {
            doHandle("Rest.Error.bodyErr", bodyField, NilMap, (dynamicArgs) -> {
                restParam.setBody(dynamicArgs);
            });
        }
        //******************method********************
        String method = meta.getMethod();
        restParam.setMethod(method);
        postHandleParam(restParam);
        return restParam;
    }

    /**
     * 处理多个参数
     *
     * @param paramMsg
     * @param paramField
     * @param parameterName
     * @throws KettleException
     */
    private List<RestParam.Info> handleList(String paramMsg, String[] paramField, String[] parameterName, Map<String, Object> localCache) throws KettleException {
        List<RestParam.Info> params = null;
        if (paramField != null && paramField.length > 0) {
            params = new ArrayList<>(paramField.length);
            for (int i = 0; i < paramField.length; i++) {
                String field = paramField[i];
                RestParam.Info info = new RestParam.Info(parameterName[i], field);
                doHandle(paramMsg, field, localCache, (dynamicArgs) -> {
                    info.setField(dynamicArgs);
                });
                params.add(info);
            }
        }
        return params;
    }

    /**
     * 处理参数
     *
     * @param err         err信息
     * @param dynamicArgs 参数
     * @param consumer    函数
     * @throws KettleException
     */
    private void doHandle(String err, String dynamicArgs, Map<String, Object> localCache, Consumer<String> consumer) throws KettleException {
        if (StringUtils.isEmpty(dynamicArgs)) {
            return;
        }
        if (dynamicArgs.startsWith(StringUtil.UNIX_OPEN) && dynamicArgs.endsWith(StringUtil.UNIX_CLOSE)) {
            String contentInfo = RestStep.getContentInfo(dynamicArgs);
            String substitute = getSubstitute(contentInfo, localCache);
            if (StringUtils.isEmpty(substitute)) {
                throwMsg(err, dynamicArgs);
            }
            dynamicArgs = substitute;
        } else if (!dynamicArgs.startsWith(StringUtil.UNIX_OPEN) && dynamicArgs.contains(StringUtil.UNIX_OPEN)) {
            Pattern regex = RestStep.regex;
            Matcher m = regex.matcher(dynamicArgs);
            while (m.find()) {
                String param = m.group(0);
                String contentInfo = RestStep.getContentInfo(param);
                String substitute = getSubstitute(contentInfo, localCache);
                if (StringUtils.isEmpty(substitute)) {
                    throwMsg(err, param);
                }
                dynamicArgs = dynamicArgs.replaceFirst(RestStep.exp, substitute);
            }
        } else if (!dynamicArgs.contains(StringUtil.UNIX_OPEN)) {
            String substitute = getSubstitute(dynamicArgs, localCache);
            if (StringUtils.isNotEmpty(substitute)) {
                dynamicArgs = substitute;
            }
        } else {
            throwMsg(err, dynamicArgs);
        }
        consumer.accept(dynamicArgs);
    }

    private String getSubstitute(String contentInfo, Map<String, Object> localCache) {
        String variable = getVariable(contentInfo);
        if (StringUtils.isEmpty(variable)) {
            if (localCache.containsKey(contentInfo)) {
                variable = localCache.get(contentInfo).toString();
            }
        }
        return variable;
    }

    /**
     * 抛出异常并记录公共方法
     *
     * @param key
     * @param param
     * @throws KettleException
     */
    private void throwMsg(String key, String param) throws KettleException {
        logError(BaseMessages.getString(PKG, key));
        throw new KettleException(BaseMessages.getString(PKG, key, param));
    }

    @Override
    public boolean init(StepMetaInterface smi, StepDataInterface sdi) {
        meta = (RestMeta) smi;
        data = (RestData) sdi;

        if (super.init(smi, sdi)) {
            data.resultFieldName = environmentSubstitute(meta.getFieldName());
            data.resultCodeFieldName = environmentSubstitute(meta.getResultCodeFieldName());
            data.resultResponseFieldName = environmentSubstitute(meta.getResponseTimeFieldName());
            data.resultHeaderFieldName = environmentSubstitute(meta.getResponseHeaderFieldName());

            // get authentication settings once
            data.realProxyHost = environmentSubstitute(meta.getProxyHost());
            data.realProxyPort = Const.toInt(environmentSubstitute(meta.getProxyPort()), 8080);
            data.realHttpLogin = environmentSubstitute(meta.getHttpLogin());
            data.realHttpPassword = Encr.decryptPasswordOptionallyEncrypted(environmentSubstitute(meta.getHttpPassword()));

            if (!meta.isDynamicMethod()) {
                data.method = environmentSubstitute(meta.getMethod());
                if (Utils.isEmpty(data.method)) {
                    logError(BaseMessages.getString(PKG, "Rest.Error.MethodMissing"));
                    return false;
                }
            }

            data.trustStoreFile = environmentSubstitute(meta.getTrustStoreFile());
            data.trustStorePassword = environmentSubstitute(meta.getTrustStorePassword());

            String applicationType = Const.NVL(meta.getApplicationType(), "");
            if (applicationType.equals(RestMeta.APPLICATION_TYPE_XML)) {
                data.mediaType = MediaType.APPLICATION_XML_TYPE;
            } else if (applicationType.equals(RestMeta.APPLICATION_TYPE_JSON)) {
                data.mediaType = MediaType.APPLICATION_JSON_TYPE;
            } else if (applicationType.equals(RestMeta.APPLICATION_TYPE_OCTET_STREAM)) {
                data.mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
            } else if (applicationType.equals(RestMeta.APPLICATION_TYPE_XHTML)) {
                data.mediaType = MediaType.APPLICATION_XHTML_XML_TYPE;
            } else if (applicationType.equals(RestMeta.APPLICATION_TYPE_FORM_URLENCODED)) {
                data.mediaType = MediaType.APPLICATION_FORM_URLENCODED_TYPE;
            } else if (applicationType.equals(RestMeta.APPLICATION_TYPE_ATOM_XML)) {
                data.mediaType = MediaType.APPLICATION_ATOM_XML_TYPE;
            } else if (applicationType.equals(RestMeta.APPLICATION_TYPE_SVG_XML)) {
                data.mediaType = MediaType.APPLICATION_SVG_XML_TYPE;
            } else if (applicationType.equals(RestMeta.APPLICATION_TYPE_TEXT_XML)) {
                data.mediaType = MediaType.TEXT_XML_TYPE;
            } else {
                data.mediaType = MediaType.TEXT_PLAIN_TYPE;
            }
            try {
                setConfig();
            } catch (Exception e) {
                logError(BaseMessages.getString(PKG, "Rest.Error.Config"), e);
                return false;
            }
            return true;
        }
        return false;
    }

    @Override
    public void dispose(StepMetaInterface smi, StepDataInterface sdi) {
        meta = (RestMeta) smi;
        data = (RestData) sdi;

        data.config = null;
        data.headerNames = null;
        data.indexOfHeaderFields = null;
        data.paramNames = null;
        super.dispose(smi, sdi);
    }

    private static boolean isFirstStep(TransMeta transMeta, StepMeta currentStep) {
        // 获取转换的所有连接
        List<TransHopMeta> transHops = transMeta.getTransHops();

        for (TransHopMeta hop : transHops) {
            // 获取连接的源和目标步骤
            StepMeta targetStep = hop.getToStep();

            // 如果目标步骤是当前步骤，那么当前步骤不是第一个节点
            if (targetStep.equals(currentStep)) {
                return false;
            }
        }

        // 如果没有找到与当前步骤相连的连接，那么当前步骤是第一个节点
        return true;
    }


}
